Skip to content

Spring学习_IOC

1、IOC是什么

IoC & AOP 不是 Spring 提出来的,它们在 Spring 之前已经存在了,只不过当时更加偏向于理论。Spring 在技术层次将这两个思想进行了很好的实现。

IOC (Inversion of Control )指的是控制反转/反转控制。

IOC 理论说明

例如:现有类 A 依赖于类 B

  • 在传统的开发方式中,往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来
  • 使用IOC思想的开发方式:不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面获取。

控制反转主要指的是将对象的创建和对象之间的关系维护交给IOC容器去管理,而不是由程序员手动进行管理。在传统的应用中,对象的创建通常是由程序员通过new关键字来完成的,而对象之间的关系也需要由程序员进行硬编码。

image.png

  • 控制 :指的是对象创建(实例化、管理)的权力
  • 反转 :控制权交给外部环境(IoC 容器)

IOC 和 DI 的关系

其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

通俗来说就是IoC是设计思想,DI是实现方式

IoC是设计思想,DI是实现方式。

IOC 容器

在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。

Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。

什么是 Spring Bean

Bean 代指的就是那些被 IoC 容器所管理的对象。

我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。

2、IOC 配置的三种方式

主流方式是 注解 + Java 配置.

Spring框架提供了三种主要的IoC配置方式:XML配置、注解配置和Java配置。开发者可以选择其中一种或混合使用这些方式,来声明对象的依赖关系和配置信息。

Spring容器会根据这些配置信息,自动扫描并加载所需的对象,实现对象的创建、装配和管理。

xml 配置

顾名思义,就是将bean的信息配置.xml文件里,通过Spring加载文件为我们创建bean。

这种方式出现很多早前的SSM项目中,将第三方类库或者一些配置工具类都以这种方式进行配置,主要原因是由于第三方类不支持Spring注解。

  • 优点: 可以使用于任何场景,结构清晰,通俗易懂
  • 缺点: 配置繁琐,不易维护,枯燥无味,扩展性差

举例

  1. 配置xx.xml文件
  2. 声明命名空间和配置bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- services -->
    <bean id="userService" class="tech.pdai.springframework.service.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>
    <!-- more bean definitions for services go here -->
</beans>

Java 配置

将类的创建交给我们配置的JavcConfig类来完成,Spring只负责维护和管理,采用纯Java创建方式。

其本质上就是把在XML上的配置声明转移到Java配置类中

  • 优点:适用于任何场景,配置方便,因为是纯Java代码,扩展性高,十分灵活
  • 缺点:由于是采用Java类的方式,声明不明显,如果大量配置,可读性比较差

举例

  1. 创建一个配置类, 添加@Configuration注解声明为配置类
  2. 创建方法,方法上加上@bean,该方法用于创建实例并返回,该实例创建后会交给spring管理,方法名建议与实例名相同(首字母小写)。注:实例类不需要加任何注解
/**
 * @author pdai
 */
@Configuration
public class BeansConfig {

    /**
     * @return user dao
    */
    @Bean("userDao")
    public UserDaoImpl userDao() {
        return new UserDaoImpl();
    }

    /**
     * @return user service
    */
    @Bean("userService")
    public UserServiceImpl userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(userDao());
        return userService;
    }
}

注解配置

通过在类上加注解的方式,来声明一个类交给Spring管理,Spring会自动扫描带有@Component,@Controller,@Service,@Repository这四个注解的类,然后帮我们创建并管理,前提是需要先配置Spring的注解扫描器。

  • 优点:开发便捷,通俗易懂,方便维护。
  • 缺点:具有局限性,对于一些第三方资源,无法添加注解。只能采用XML或JavaConfig的方式配置

举例

  1. 对类添加@Component相关的注解,比如@Controller,@Service,@Repository
  2. 设置ComponentScan的basePackage, 比如<context:component-scan base-package='tech.pdai.springframework'>, 或者@ComponentScan("tech.pdai.springframework")注解,或者 new AnnotationConfigApplicationContext("tech.pdai.springframework")指定扫描的basePackage.
/**
 * @author pdai
 */
@Service
public class UserServiceImpl {

    /**
     * user dao impl.
     */
    @Autowired
    private UserDaoImpl userDao;

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return userDao.findUserList();
    }

}

3、依赖注入的三种方式

参考看一下这个: https://zhuanlan.zhihu.com/p/557140781

依赖注入有三种主要的实现方式:属性注入、Setter注入和构造方法注入。

属性注入最简单,但通用性较差。Spring官方推荐使用构造方法注入,因为它支持注入不可变对象,通用性更强。对于可变对象,可以考虑使用Setter注入。

属性注入

代码示例

  1. 属性注入(Field Injection)

属性注入是我们最熟悉,也是日常开发中使用最多的一种注入方式(直接在类的字段上注入依赖)

这种方式在某些场景下很方便,但可能会导致难以测试和难以维护的代码,因为它隐藏了类的依赖。

示例代码:

@Component
public class MyService {

    @Autowired
    private DependencyClass dependency;

    // ... 其他方法 ...
}

@Component
public class DependencyClass {
    // ... 类的实现 ...
}

优点是简单易用,缺点也随之而来

  1. 功能性问题:无法注入一个不可变的对象(final 修饰的对象);
  2. 通用性问题:只能适应于 IoC 容器;
  3. 设计原则问题:更容易违背单一设计原则。

Setter注入

  1. Setter注入(Setter Injection)

在Setter注入中,Spring通过调用类的setter方法来注入依赖。

示例代码:

@Component
public class MyService {

    private DependencyClass dependency;

    @Autowired
    public void setDependency(DependencyClass dependency) {
        this.dependency = dependency;
    }

    // ... 其他方法 ...
}

@Component
public class DependencyClass {
    // ... 类的实现 ...
}

setter 的方式可能会麻烦一点,不过他是完全符合单一职责的设计原则,每一个 Setter 只针对一个对象。

缺点:

  1. 不能注入不可变对象(final 修饰的对象);
  2. 注入的对象可被修改

构造方法注入

  1. 构造方法注入(Constructor Injection)

这种方式通过类的构造方法来注入依赖。这是最推荐的方式,因为它可以确保所需的依赖项在对象创建时即被提供,从而保证了对象的不变性和依赖的不可更改性。

示例代码:

@Component
public class MyService {

    private final DependencyClass dependency;

    @Autowired
    public MyService(DependencyClass dependency) {
        this.dependency = dependency;
    }

    // ... 其他方法 ...
}

@Component
public class DependencyClass {
    // ... 类的实现 ...
}

在这个例子中,MyService 类依赖于 DependencyClass

当Spring创建 MyService 的实例时,它会查找 DependencyClass 的实例并通过构造方法注入它。

从Spring 4.3开始,当一个类只有一个构造函数时,@Autowired 注解是可选的(可以省略)。Spring会自动将这个唯一的构造函数用作依赖注入的入口。

上面的代码可以简化为:

@Component
public class MyService {

    private final DependencyClass dependency;

    public MyService(DependencyClass dependency) {
        this.dependency = dependency;
    }

    // ... 其他方法 ...
}

@Component
public class DependencyClass {
    // ... 类的实现 ...
}

这种写法符合“约定优于配置”的原则,减少了不必要的注解使用。


参考